package com.zws.cucumber.expr;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zws.cucumber.util.ZwsPreparation;
import com.zws.cucumber.util.colorfullog.ZwsColorfulLogger;
import com.zws.cucumber.var.ZwsVar;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;

import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.SimpleScriptContext;
import java.util.*;
import java.util.function.Consumer;

import static com.zws.cucumber.expr.ZwsExpr.aliases;

/**
 * author: zws
 */
public class ZwsExprBuiltInGenerators {

    private static final Logger logger = ZwsColorfulLogger.of(ZwsExprBuiltInGenerators.class);
    private static final ObjectMapper objectMapper = new ObjectMapper();
    public static final ScriptEngineManager factory = new ScriptEngineManager(ZwsExprBuiltInGenerators.class.getClassLoader());
    public static final ScriptEngine engine = factory.getEngineByName("groovy");
    public static Properties properties;

    public static void init() {
        ZwsExpr.registerGenerator("new_json_obj",
            ZwsExprBuiltInGenerators::newObjectFromJson);
        ZwsExpr.registerGenerator("new_json_arr",
            ZwsExprBuiltInGenerators::newJsonArray);
        ZwsExpr.registerGenerator("new_int", aliases("int"),
            ZwsExprBuiltInGenerators::newInteger);
        ZwsExpr.registerGenerator("new_null",
            ZwsExprBuiltInGenerators::newNull);
        ZwsExpr.registerGenerator("new_str", aliases(), true,
            ZwsExprBuiltInGenerators::newString);
        ZwsExpr.registerGenerator("new_str_empty", aliases("str_empty"),
            ZwsExprBuiltInGenerators::newStringEmpty);
        ZwsExpr.registerGenerator("new_str_blank", aliases("str_blank"),
            ZwsExprBuiltInGenerators::newStringBlank);
        ZwsExpr.registerGenerator("new_str_from_file", aliases("str_f"),
            ZwsExprBuiltInGenerators::newStringFromFile);
        ZwsExpr.registerGenerator("new_str_from_expr", aliases("str_e"),
            ZwsExprBuiltInGenerators::newStringFromExpr);
        ZwsExpr.registerGenerator("new_str_from_prop", aliases("str_p"),
            ZwsExprBuiltInGenerators::newStringFromProp);
        ZwsExpr.registerGenerator("new_true", aliases("true"),
            ZwsExprBuiltInGenerators::newTrue);
        ZwsExpr.registerGenerator("new_false", aliases("false"),
            ZwsExprBuiltInGenerators::newFalse);
        ZwsExpr.registerGenerator("new_obj_from_e", aliases("obj_e"),
            ZwsExprBuiltInGenerators::newObjectFromExpr);
        ZwsExpr.registerGenerator("new_obj_from_j", aliases("obj_j"),
                ZwsExprBuiltInGenerators::newObjectFromJson);

        ZwsExpr.registerGenerator("bin_f", ZwsExprBuiltInGenerators::newBinaryFromFile);
    }

    private static Pair<Object, Object> newObjectFromJson(List<String> args, String pattern, Object actual) {
        if (StringUtils.isBlank(pattern)) {
            return Pair.of(new HashMap<>(), actual);
        } else {
            try {
                return Pair.of(objectMapper.readValue(pattern, Object.class), actual);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private static Pair<Object, Object> newJsonArray(List<String> args, String pattern, Object actual) {
        return Pair.of(new ArrayList<>(), actual);
    }

    private static Pair<Object, Object> newInteger(List<String> args, String pattern, Object actual) {
        return Pair.of(Integer.valueOf(pattern), actual);
    }

    private static Pair<Object, Object> newNull(List<String> args, String pattern, Object actual) {
        return Pair.of(null, actual);
    }

    private static Pair<Object, Object> newString(List<String> args, String pattern, Object actual) {
        return Pair.of(pattern, actual);
    }

    private static Pair<Object, Object> newStringEmpty(List<String> args, String pattern, Object actual) {
        return Pair.of("", actual);
    }

    private static Pair<Object, Object> newStringBlank(List<String> args, String pattern, Object actual) {
        int len = 1;
        if (args.size() == 1) {
            len = Integer.parseInt(args.get(0));
        }
        char[] charArray = new char[len];
        Arrays.fill(charArray, ' ');
        return Pair.of(new String(charArray), actual);
    }

    private static Pair<Object, Object> newStringFromFile(List<String> args, String pattern, Object actual) {
        return Pair.of(ZwsPreparation.loadFile(pattern), actual);
    }

    private static Pair<Object, Object> newStringFromExpr(List<String> args, String pattern, Object actual) {
        ScriptContext scriptContext = new SimpleScriptContext();
        ZwsExprUtils.pushVarBlockAndFillAttrs(scriptContext);
        if (args.contains("debug")) {
            ZwsExprUtils.printAttributeOfScriptContext(scriptContext);
        }
        try {
            if (engine == null) {
                logger.error("");
            }
            Object object = engine.eval(pattern, scriptContext);
            return Pair.of(object != null ? object.toString() : null, actual);
        } catch (Throwable t) {
            logger.debug("", t);
            throw new RuntimeException(t);
        } finally {
            ZwsVar.popProcedureBlock();
        }
    }

    private static Pair<Object, Object> newStringFromProp(List<String> args, String pattern, Object actual) {
        if (properties == null) {
            properties = new Properties();
            ResourceLoader loader = new DefaultResourceLoader();

            Consumer<String> propLoader = (s) -> {
                try {
                    logger.debug("load " + s);
                    properties.load(loader.getResource(s).getInputStream());
                } catch (Exception e) {
                    logger.error("fail to load " + s + ", cause: " + e.getMessage());
                }
            };
            propLoader.accept("application.test.properties");
            propLoader.accept("cm.config.properties");
            propLoader.accept("ct.cucumber.template.properties");
        }
        return Pair.of(properties.getProperty(args.get(0)), actual);
    }

    private static Pair<Object, Object> newObjectFromExpr(List<String> args, String pattern, Object actual) {
        ScriptContext scriptContext = new SimpleScriptContext();
        ZwsExprUtils.pushVarBlockAndFillAttrs(scriptContext);
        if (args.contains("debug")) {
            ZwsExprUtils.printAttributeOfScriptContext(scriptContext);
        }
        try {
            if (engine == null) {
                logger.error("");
            }
            Object object = engine.eval(pattern, scriptContext);
            return Pair.of(object, actual);
        } catch (Throwable t) {
            logger.debug("", t);
            throw new RuntimeException(t);
        } finally {
            ZwsVar.popProcedureBlock();
        }
    }

    private static Pair<Object, Object> newTrue(List<String> args, String pattern, Object actual) {
        return Pair.of(Boolean.TRUE, actual);
    }

    private static Pair<Object, Object> newFalse(List<String> args, String pattern, Object actual) {
        return Pair.of(Boolean.FALSE, actual);
    }

    private static Pair<Object, Object> newBinaryFromFile(List<String> args, String pattern, Object actual) {
        return Pair.of(ZwsPreparation.loadBinaryFile(pattern), actual);
    }

}
